import Dep, {
  pushTarget,
  popTarget
} from './dep'
import {
  parsePath
} from '../util/lang'

let uid = 0

export default function Watcher(vm, expOrFn, cb, options) {
  // 缓存id
  this.id = ++uid
  // 缓存回调函数
  this.cb = cb
  // 缓存Vue实例
  this.vm = vm
  // 初始化依赖列表
  this.deps = []
  // 初始化依赖列表id
  this.depIds = new Set()
  if (options) {
    this.user = !!options.user // user表示是否用户定义的Watcher，即我们在new Vue({watch:{}})里的watch
    this.lazy = !!options.lazy
  } else {
    this.user = this.lazy = false
  }
  // 设置dirty属性
  this.dirty = this.lazy // 用于渲染时不把计算watcher设置成Dep.target
  // 设置getter属性
  this.getter = typeof expOrFn === 'function' ? expOrFn : parsePath(expOrFn)
  // 获取当前值
  this.value = this.lazy ? undefined : this.get()
}
// 挂载get方法
Watcher.prototype.get = function () {
  let value
  const vm = this.vm
  pushTarget(this)
  // 读取当前属性，触发依赖收集
  value = this.getter.call(vm, vm)
  popTarget()
  return value
}
// 挂载update方法
Watcher.prototype.update = function () {
  if (this.lazy) {
    this.dirty = true
  } else {
    this.run()
  }
}
// 挂载addDep方法，添加依赖
Watcher.prototype.addDep = function (dep) {
  const id = dep.id
  // 如果当前依赖列表中没有待加入依赖
  if (!this.depIds.has(id)) {
    this.deps.push(dep)
    this.depIds.add(id)
    dep.addSub(this)
  }
}
// 获取当前值，将dirty属性设为false
Watcher.prototype.evaluate = function () {
  // 触发依赖收集
  this.value = this.get()
  this.dirty = false
}
// 添加依赖
Watcher.prototype.depend = function () {
  let i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}

Watcher.prototype.run = function () {
  // 触发依赖收集
  const value = this.get()
  // 变化时才执行回调函数
  if (value !== this.value) {
    const oldValue = this.value
    this.value = value
    // 如果是用户自定义watcher
    if (this.user) {
      try {
        this.cb.call(this.vm, value, oldValue)
      } catch (e) {
        console.error(`callback for watcher "${this.expression}"`)
      }
    } else {
      this.cb.call(this.vm, value, oldValue)
    }
  }
}

Watcher.prototype.teardown = function () {

}